package weka.classifiers.functions._HC_m3;

import java.io.FileReader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Vector;

import weka.classifiers.Classifier;
import weka.classifiers.lazy.IB1;
import weka.classifiers.lazy.IBk;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Option;
import weka.core.RevisionUtils;
import weka.core.Utils;

public class _HC_PriorDivider extends _HC_DataDivider {

	protected Classifier m_InnerClassifier = new IB1();

	protected int m_PriorClassIndex = -1;

	protected String m_PriorDataFile = "";

	protected double m_IgnoreThreshold = 0.01;

	protected Instances m_PriorInstances;

	protected HashMap<Double, HashMap<Double, Vector<Integer>>> m_Block = new HashMap<Double, HashMap<Double, Vector<Integer>>>();

	@Override
	public void buildDivider(Instances _instances) {
		m_Instances = new Instances(_instances);
		initialize();
		try {
			loadAndCheckPrior();
			makeBlock();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	protected void makeBlock() throws Exception {
		// //DEBUG
		// IB1 tmpIb1 = new IB1();
		// tmpIb1.buildClassifier(m_PriorInstances);
		// double rc = m_PriorInstances.instance(1252).classValue();
		// double cc = tmpIb1.classifyInstance(m_PriorInstances.instance(1252));
		// rc = m_PriorInstances.instance(1252).classValue();
		// cc = tmpIb1.classifyInstance(m_PriorInstances.instance(1252));
		// if (rc != cc) {
		// System.out.println("@" + 1252 + " : " + rc + " vs. " + cc);
		// }
		// //DEBUG

		m_InnerClassifier.buildClassifier(m_PriorInstances);
		// Instance pointIns = new Instance(m_PriorInstances.instance(0));
		// Instances tmpDataset = new Instances(m_PriorInstances, 0);
		for (int i = 0; i < m_Instances.numInstances(); ++i) {
			// Instance tmp = new Instance(pointIns);
			// tmp.setDataset(m_PriorInstances);
			// for (int j = 0; j < tmp.numAttributes(); ++j) {
			// if (j == pointIns.classIndex()) {
			// continue;
			// }
			// tmp.modifyValue(j, m_Instances.instance(i).value(j));
			// }
			double bclass = m_InnerClassifier.classifyInstance(m_Instances
					.instance(i));
			double rclass = m_Instances.instance(i).classValue();
			// if (i == 1097 || bclass == 3.0){
			// System.out.println("br : " + bclass + " vs. " + rclass);
			// }

			// // DEBUG
			// if (i == 1252) {
			// // for (int j = 0; j < tmp.numAttributes(); ++j) {
			// // System.out.println(m_Instances.instance(i).value(j) + " "
			// // + tmp.value(j));
			// // }
			//
			// IB1 ttmpIb1 = new IB1();
			// ttmpIb1.buildClassifier(m_PriorInstances);
			// double trc = m_PriorInstances.instance(i).classValue();
			// double tcc = tmpIb1.classifyInstance(m_PriorInstances
			// .instance(i));
			// rc = m_PriorInstances.instance(i).classValue();
			// cc = tmpIb1.classifyInstance(m_PriorInstances.instance(i));
			// System.out.println(trc);
			// System.out.println(tcc);
			// // System.out.println(tmpIb1.classifyInstance(tmp));
			// }
			// // DEBUG

			if (!m_Block.containsKey(rclass)) {
				m_Block.put(rclass, new HashMap<Double, Vector<Integer>>());
			}
			HashMap<Double, Vector<Integer>> outPointer = m_Block.get(rclass);
			if (!outPointer.containsKey(bclass)) {
				outPointer.put(bclass, new Vector<Integer>());
			}
			Vector<Integer> inPointer = outPointer.get(bclass);
			inPointer.add(i);
		}
	}

	protected void loadAndCheckPrior() throws Exception {
		if (m_PriorDataFile.length() == 0) {
			throw new Exception("No prior data file");
		}
		FileReader reader = new FileReader(m_PriorDataFile);
		m_PriorInstances = new Instances(reader);
		if (m_PriorClassIndex >= 0) {
			m_PriorInstances.setClassIndex(m_PriorClassIndex);
		}
		reader.close();
		if (m_PriorInstances.numAttributes() != m_Instances.numAttributes()) {
			throw new Exception("Prior attributes number not match");
		}
		if (m_PriorInstances.classIndex() != m_Instances.classIndex()) {
			throw new Exception("Prior class index not match");
		}
		// if (!m_PriorInstances.classAttribute().isNominal()) {
		// throw new Exception("Prior class is not nominal");
		//		}
		// for (int i = 0; i < m_Instances.numAttributes(); ++i) {
		// if (i == m_PriorInstances.classIndex()) {
		// continue;
		// }
		// if (m_Instances.attribute(i).equals(m_PriorInstances.attribute(i))) {
		// throw new Exception("Prior attribute " + i + " not match");
		// }
		// }
	}

	protected void initialize() {
		for (int i = 0; i < m_Instances.numInstances(); ++i) {
			double c = m_Instances.instance(i).classValue();
			int v = 0;
			if (m_LabelCount.containsKey(c)) {
				v = m_LabelCount.get(c);
			}
			++v;
			m_LabelCount.put(c, v);
		}
	}

	@Override
	public Instances[] divide(double _classValue) {
		Instances[] ret;
		int length = 0;
		int count = m_LabelCount.get(_classValue);
		HashMap<Double, Vector<Integer>> pointer = m_Block.get(_classValue);
		if (pointer.size() == 0) {
			return new Instances[0];
		}
		for (double key : pointer.keySet()) {
			int size = pointer.get(key).size();
			if (size < count * m_IgnoreThreshold) {
				continue;
			}
			++length;
		}
		ret = new Instances[length];
		int cid = 0;
		for (double key : pointer.keySet()) {
			int size = pointer.get(key).size();
			if (size < count * m_IgnoreThreshold) {
				continue;
			}
			ret[cid] = new Instances(m_Instances, 0);
			Vector<Integer> inPointer = pointer.get(key);
			for (int i = 0; i < inPointer.size(); ++i) {
				int iid = inPointer.get(i);
				Instance ins = m_Instances.instance(iid);
				ret[cid].add(ins);
			}
			++cid;
		}
		return ret;
	}

	public String getPriorData() {
		return m_PriorDataFile;
	}

	public void setPriorData(String _dataFile) {
		m_PriorDataFile = _dataFile;
	}

	public int getPriorClassIndex() {
		return m_PriorClassIndex;
	}

	public void setPriorClassIndex(int _id) {
		m_PriorClassIndex = _id;
	}

	public Classifier getInnerClassifier() {
		return m_InnerClassifier;
	}

	public void setInnerClassifier(Classifier _cl) {
		m_InnerClassifier = _cl;
	}

	public double getIgnoreThreshold() {
		return m_IgnoreThreshold;
	}

	public void setIgnoreThreshold(double _it) {
		m_IgnoreThreshold = _it;
	}

	@Override
	public String[] getOptions() {
		String[] ret = new String[8];
		int id = 0;
		ret[id++] = "-F";
		ret[id++] = getPriorData();
		ret[id++] = "-I";
		ret[id++] = "" + getPriorClassIndex();
		ret[id++] = "-H";
		ret[id++] = "" + getIgnoreThreshold();
		ret[id++] = "-C";
		ret[id++] = getInnerClassifier().getClass().getName() + " "
				+ Utils.joinOptions(getInnerClassifier().getOptions());
		return ret;
	}

	public Enumeration listOptions() {
		Vector<Option> ret = new Vector<Option>();
		ret.add(new Option("Prior Data File", "F", 1, "-F <String>"));
		ret.add(new Option("Prior Data Fiter / Classifier", "C", 1,
				"-C <String>"));
		ret.add(new Option("Ignore Threshold", "T", 1, "-H <num>"));
		ret.add(new Option("Prior Data index", "I", 1, "-I <num>"));
		return ret.elements();
	}

	public void setOptions(String[] options) throws Exception {
		String tmp = Utils.getOption('F', options);
		if (tmp == null) {
			throw new Exception("no prior data file");
		} else {
			setPriorData(tmp);
		}

		tmp = Utils.getOption('I', options);
		if (tmp == null) {
			setPriorClassIndex(-1);
		} else {
			setPriorClassIndex(Integer.valueOf(tmp));
		}

		tmp = Utils.getOption('H', options);
		if (tmp == null) {
			setIgnoreThreshold(0.05);
		} else {
			setIgnoreThreshold(Double.valueOf(tmp));
		}

		tmp = Utils.getOption('C', options);
		if (tmp.length() != 0) {
			String[] opt = Utils.splitOptions(tmp);
			if (opt.length == 0) {
				throw new _HC_NullClassifierException();
			}
			String name = opt[0];
			opt[0] = "";
			Classifier c = (Classifier) Utils.forName(Classifier.class, name,
					opt);
			setInnerClassifier(c);
		} else {
			throw new _HC_NullClassifierException();
		}

		Utils.checkForRemainingOptions(options);
	}

	public String getRevision() {
		return RevisionUtils.extract("$Revision: _HC_ 1.10 $");
	}

}
